from __future__ import annotations

"""
Pydantic 模型（请求/响应）
-------------------------
职责：
- 定义接口的输入输出结构与字段校验
- 为 Swagger/OpenAPI 提供类型与描述

说明：
- `ConfigDict(from_attributes=True)` 允许从 ORM 对象直接构建响应
- 可选字段用于 PATCH/部分更新场景
"""

import datetime as dt
from decimal import Decimal
from typing import Any, Dict, List, Optional

from pydantic import BaseModel, ConfigDict, EmailStr, Field

from .models import (
    MarketType,
    OrderStatus,
    OrderType,
    PositionAction,
    RoleEnum,
    ScriptStatus,
    SignalStatus,
    TradeSide,
)


class APIMessage(BaseModel):
    message: str


class RegisterRequest(BaseModel):
    """用户注册请求体。默认角色为普通用户。"""
    email: EmailStr = Field(description="邮箱，做为登录名与账户名")
    password: str = Field(description="登录密码")
    role: RoleEnum = Field(default=RoleEnum.user, description="角色：user/admin（默认 user）")


class LoginRequest(BaseModel):
    """用户登录请求体。支持可选的 2FA 验证码。"""
    email: EmailStr = Field(description="邮箱")
    password: str = Field(description="登录密码")
    otp_code: Optional[str] = Field(default=None, description="二次验证码（若启用 2FA）")


class TokenResponse(BaseModel):
    """登录成功后返回的访问/刷新令牌。"""
    access_token: str
    refresh_token: str
    token_type: str = "bearer"


class RefreshRequest(BaseModel):
    """使用刷新令牌获取新的访问令牌。"""
    refresh_token: str


class UserRead(BaseModel):
    """用户信息响应。"""
    id: int
    email: EmailStr
    role: RoleEnum
    tv_enabled: bool
    global_pause: bool
    created_at: dt.datetime

    model_config = ConfigDict(from_attributes=True)


class UserSettingsUpdate(BaseModel):
    """用户设置更新（部分字段可选）。"""
    tv_enabled: Optional[bool] = None
    global_pause: Optional[bool] = None


class AccountInfo(BaseModel):
    """交易账户信息摘要。"""
    account_name: str
    connector: Optional[str] = None
    status: Optional[str] = None


class AccountBindRequest(BaseModel):
    """绑定/创建账户请求。"""
    account_name: str = Field(description="账户名，必须等于登录邮箱")


class AccountConnectorBindRequest(BaseModel):
    """为账户绑定交易所连接器与凭据。"""
    account_name: str = Field(description="账户名，必须等于登录邮箱")
    connector_name: str = Field(description="交易所连接器名称，如 gate_io 或 gate_io_perpetual")
    credentials: Dict[str, Any] = Field(description="交易所 API 凭据键值对，按 hb-api 要求")


class ScriptCreateRequest(BaseModel):
    """创建策略请求体：仅保留名称、交易对与市场类型。"""
    name: str = Field(description="策略名称，便于识别")
    symbol: str = Field(description="交易对，如 BTC-USDT")
    market_type: MarketType = Field(description="市场类型：spot/perpetual")


class ScriptUpdateStatusRequest(BaseModel):
    """策略状态更新（例如启用/暂停）。"""
    action: str


class ScriptUpdateRequest(BaseModel):
    """策略基础信息部分更新。"""
    name: Optional[str] = Field(default=None, description="策略名称")
    symbol: Optional[str] = Field(default=None, description="交易对")
    market_type: Optional[MarketType] = Field(default=None, description="市场类型：spot/perpetual")


class ScriptResponse(BaseModel):
    """策略列表/详情响应。"""
    id: int
    name: str
    symbol: str
    market_type: MarketType
    status: ScriptStatus
    created_at: dt.datetime

    model_config = ConfigDict(from_attributes=True)


class ScriptCreateResponse(ScriptResponse):
    """创建策略成功时返回额外信息（webhook 地址与明文密钥）。"""
    webhook_url: str
    body_secret: str
    url_token: str
    payload_template: Dict[str, Any]
    pine_snippet: str


class SubscriptionRequest(BaseModel):
    """订阅策略请求体。

    - `max_notional`、`slippage_bps` 可选：后端会使用默认风控值
    - `allocation` 为资金配置（JSON），由前端表单传入
    """
    script_id: int = Field(description="策略 ID")
    enabled: bool = Field(default=True, description="是否启用该订阅")
    allocation: Dict[str, Any] = Field(description="资金配置，如 {mode:'fixed_quote',value:10}")
    max_notional: Optional[Decimal] = Field(default=None, description="名义金额上限（USDT），留空走默认")
    slippage_bps: Optional[int] = Field(default=None, description="滑点保护阈值（bps），留空走默认")
    connector_name: str = Field(description="交易所连接器名称，需已绑定")
    account_name: Optional[str] = Field(default=None, description="账户名，留空默认等于邮箱")


class SubscriptionResponse(BaseModel):
    """订阅信息响应。包含策略基础信息与风控参数。"""
    script_id: int
    script_name: str
    enabled: bool
    allocation: Dict[str, Any]
    max_notional: Optional[Decimal]
    slippage_bps: Optional[int]
    connector_name: Optional[str]
    account_name: Optional[str]
    symbol: str
    market_type: MarketType
    status: ScriptStatus


class SubscriptionUpdateRequest(BaseModel):
    # 用于部分更新，全部为可选
    enabled: Optional[bool] = Field(default=None, description="是否启用该订阅")
    allocation: Optional[Dict[str, Any]] = Field(default=None, description="资金配置对象")
    max_notional: Optional[Decimal] = Field(default=None, description="名义金额上限（USDT）")
    slippage_bps: Optional[int] = Field(default=None, description="滑点保护阈值（bps）")
    connector_name: Optional[str] = Field(default=None, description="交易所连接器名称")
    account_name: Optional[str] = Field(default=None, description="账户名")


class AdminSubscriptionEntry(BaseModel):
    """管理员视角的订阅项（包含用户/策略维度信息）。"""
    user_id: int
    user_email: EmailStr
    script_id: int
    script_name: str
    enabled: bool
    allocation: Dict[str, Any]
    max_notional: Optional[Decimal]
    slippage_bps: Optional[int]
    status: ScriptStatus
    connector_name: Optional[str]
    account_name: Optional[str]
    symbol: str
    market_type: MarketType


class WebhookSignalRequest(BaseModel):
    """TradingView Webhook 信号请求体（大小写不敏感）。

    为兼容 TradingView 文本模板，本模型将枚举类字段改为字符串，路由内统一转大写。
    """
    secret: str = Field(
        ..., description="脚本创建时返回的 body_secret 明文，用于校验请求合法性"
    )
    nonce: str = Field(
        ..., description="唯一请求标识（时间戳或UUID），用于去重与幂等保障"
    )
    trading_pair: str = Field(
        ..., description="交易对，例如 XNA-USDT（需符合目标交易所符号规范）"
    )
    target: Optional[Dict[str, Any]] = Field(
        default=None,
        description="下单资金配置对象，例如 {\"mode\":\"fixed_quote\",\"value\":10}；支持 fixed_quote/percent_of_equity 等模式",
    )
    order_type: str = Field(
        ..., description="下单类型：MARKET（市价）/LIMIT（限价），不区分大小写"
    )
    trade_type: str = Field(
        ..., description="交易方向：BUY/SELL，不区分大小写"
    )
    position_action: str = Field(
        ..., description="仓位动作：OPEN/CLOSE，不区分大小写"
    )
    price: Optional[Decimal] = Field(
        default=None,
        description="下单价格：限价单必填；市价单可为空；用于滑点保护比较预期价格",
    )


class WebhookAcceptedResponse(BaseModel):
    """信号接收成功后的回执。"""
    accepted: bool
    signal_id: int
    message: Optional[str] = Field(default=None, description="人类可读提示信息")


class RecentSignal(BaseModel):
    """近期信号记录摘要。"""
    id: int
    script_id: int
    nonce: str
    status: SignalStatus
    error: Optional[str]
    created_at: dt.datetime
    processed_at: Optional[dt.datetime]

    model_config = ConfigDict(from_attributes=True)


class OrdersSnapshotResponse(BaseModel):
    """订单快照与仓位视图聚合响应。"""
    portfolio: Dict[str, Any]
    orders: List[Dict[str, Any]]


class BroadcastOrderRequest(BaseModel):
    """批量广播下单请求（分组内多账户）。"""
    group_id: int = Field(description="分组 ID")
    connector: str = Field(description="交易所连接器名称")
    trade_type: TradeSide = Field(description="BUY/SELL")
    order_type: OrderType = Field(default=OrderType.market, description="MARKET/LIMIT")
    position_action: PositionAction = Field(default=PositionAction.open, description="OPEN/CLOSE")
    symbol: str = Field(description="交易对，如 BTC-USDT")
    price: Optional[Decimal] = Field(default=None, description="限价（市价可不填）")
    invest_percent: Decimal = Field(
        description="投入百分比，10 表示下单金额为权益的 10%",
        gt=Decimal("0"),
        le=Decimal("100"),
    )
    skip_unbound: bool = Field(
        default=False,
        description="若为 true，则未绑定目标 connector 的账户会被跳过并在结果中标记为 skipped，仅记录错误不尝试下单。",
    )


class BroadcastResultItem(BaseModel):
    """批量广播下单的单账户执行结果。"""
    account_name: str
    status: OrderStatus
    error: Optional[str] = None
    tx_ref: Optional[str] = None


class BroadcastResponse(BaseModel):
    """批量广播结果集合。"""
    results: List[BroadcastResultItem]


class AuditQuery(BaseModel):
    """审计日志查询条件。"""
    start: Optional[dt.datetime] = Field(default=None, description="起始时间")
    end: Optional[dt.datetime] = Field(default=None, description="结束时间")
    script: Optional[str] = Field(default=None, description="脚本标识过滤")
    user: Optional[str] = Field(default=None, description="用户标识过滤")
    status: Optional[str] = Field(default=None, description="状态过滤")
    limit: int = Field(default=100, le=1000, description="返回条数上限")


class AuditLogItem(BaseModel):
    """审计日志条目。"""
    id: int
    actor_id: Optional[int]
    action: str
    target: str
    payload_json: Dict[str, Any]
    created_at: dt.datetime


class AuditResponse(BaseModel):
    """审计日志响应（带总数）。"""
    items: List[AuditLogItem]
    total: int


class GroupMemberRead(BaseModel):
    """分组成员只读视图（权重字段保留扩展）。"""
    account_name: str
    connector_name: str
    weight: int
    enabled: bool


class GroupRead(BaseModel):
    """分组只读视图（含成员）。"""
    id: int
    name: str
    connector_name: str
    desc: Optional[str]
    members: List[GroupMemberRead]


class GroupCreateRequest(BaseModel):
    """广播分组创建请求。"""
    name: str = Field(description="分组名称，需唯一")
    connector_name: str = Field(description="分组绑定的交易所连接器名称")
    desc: Optional[str] = Field(default=None, description="分组描述，可选")


class GroupUpdateRequest(BaseModel):
    """广播分组更新请求（部分字段可选）。"""
    name: Optional[str] = Field(default=None, description="分组名称")
    connector_name: Optional[str] = Field(default=None, description="分组绑定的交易所连接器名称")
    desc: Optional[str] = Field(default=None, description="分组描述")


class GroupMemberCreateRequest(BaseModel):
    """广播分组新增成员请求。"""
    account_name: str = Field(description="账户名（与 hb-api 账户一致）")
    connector_name: str = Field(description="成员绑定的交易所连接器名称")
    weight: int = Field(default=1, ge=1, description="权重（正整数），当前不参与金额分配，预留扩展")
    enabled: bool = Field(default=True, description="是否启用该成员")


class GroupMemberUpdateRequest(BaseModel):
    """广播分组成员更新请求。"""
    weight: Optional[int] = Field(default=None, ge=1, description="权重（正整数），当前不参与金额分配")
    enabled: Optional[bool] = Field(default=None, description="是否启用")
    connector_name: Optional[str] = Field(default=None, description="成员绑定的交易所连接器名称")


class GroupListResponse(BaseModel):
    """广播分组分页列表响应。"""
    total: int
    page: int
    page_size: int
    items: List[GroupRead]


class GroupSummary(BaseModel):
    """分组公共摘要（普通用户可见）。"""
    id: int
    name: str
    connector_name: str
    desc: Optional[str]
    member_count: int
    joined: bool


class GroupJoinRequest(BaseModel):
    """普通用户加入分组请求。"""
    account_name: Optional[str] = Field(
        default=None,
        description="加入分组使用的账户名，默认等于当前登录邮箱",
    )
    connector_name: str = Field(description="加入分组时选择的交易所连接器名称")
    weight: Optional[int] = Field(
        default=1,
        ge=1,
        description="加入时的权重（正整数，默认 1）",
    )



# 下单请求/响应

class PlaceOrderRequest(BaseModel):
    """用户下单请求体。

    - 仅已绑定目标 `connector_name` 的账户可下单
    - `account_name` 留空时默认等于当前登录邮箱
    - `trade_type`/`order_type`/`position_action` 需传大写枚举值（BUY/SELL、MARKET/LIMIT、OPEN/CLOSE）
    """
    connector_name: str = Field(description="交易所连接器名称，例如 gate_io 或 gate_io_perpetual")
    trading_pair: str = Field(description="交易对，例如 XNA-USDT")
    trade_type: TradeSide = Field(description="BUY/SELL")
    order_type: OrderType = Field(description="MARKET/LIMIT")
    position_action: PositionAction = Field(description="OPEN/CLOSE")
    amount: Decimal = Field(description="下单数量或等价数量（按 hb-api 语义）")
    price: Optional[Decimal] = Field(default=None, description="限价单价格；市价单可不填或填 0")
    account_name: Optional[str] = Field(default=None, description="账户名，默认等于登录邮箱")


class PlaceOrderResponse(BaseModel):
    """下单响应。"""
    ok: bool
    order_id: Optional[str] = None
    tx_ref: Optional[str] = None
    hb_response: Dict[str, Any]


class TradesQueryRequest(BaseModel):
    """成交记录搜索请求体（透传至 hb-api `/trading/orders/search`）。"""
    limit: int = Field(default=100, le=1000, description="返回条数上限")
    start_time: int = Field(default=0, description="起始时间（秒或毫秒，按 hb-api 支持）")
    end_time: int = Field(default=0, description="结束时间（0 表示当前）")
    cursor: Optional[str] = Field(default=None, description="分页游标（可选）")
    account_names: List[str | EmailStr] | str | EmailStr = Field(
        default_factory=list,
        description="账户名或列表；留空默认使用当前登录邮箱"
    )
    connector_names: List[str] = Field(default_factory=list, description="交易所连接器列表；为空表示全部")
    trading_pairs: List[str] = Field(default_factory=list, description="交易对过滤，大小写不敏感；为空表示全部")
    statuses: List[str] = Field(default_factory=list, description="订单状态过滤，大小写不敏感；为空表示全部")
    trade_types: List[str] = Field(default_factory=list, description="成交方向过滤（BUY/SELL），大小写不敏感；为空表示全部")


class TradesResponse(BaseModel):
    """成交记录响应（按 hb-api 回包透传）。"""
    items: List[Dict[str, Any]] | Dict[str, Any] | List[Any] | Dict[str, Any]
